{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {},
  "cells": [
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "   <a target=\"_blank\" href=\"https://labelbox.com\" ><img src=\"https://labelbox.com/blog/content/images/2021/02/logo-v4.svg\" width=256/></a>\n",
        "</td>"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "<a href=\"https://colab.research.google.com/github/Labelbox/labelbox-python/blob/master/examples/basics/ontologies.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"></a>\n",
        "</td>\n",
        "\n",
        "<td>\n",
        "<a href=\"https://github.com/Labelbox/labelbox-python/tree/master/examples/basics/ontologies.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://img.shields.io/badge/GitHub-100000?logo=github&logoColor=white\" alt=\"GitHub\"></a>\n",
        "</td>"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Ontologies\n",
        "* An ontology is a collection different tools and classifications that can be used within a project's editor. Each tool or classification is called a \"Feature Schema\". \n",
        "* Feature Schemas contain information about the tool such as the kind, the name, all subclasses, and other information related to a tool. Feature Schemas can be shared between ontologies. \n",
        "\n",
        "* Helpful Links:\n",
        "    * [Ontology documentation](https://docs.labelbox.com/docs/labelbox-ontology)\n",
        "    * [Project Setup Using Ontologies](https://github.com/Labelbox/labelbox-python/blob/master/examples/project_configuration/project_setup.ipynb)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "!pip install labelbox -q"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "import labelbox as lb\n",
        "import json"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# API Key and Client\n",
        "Provide a valid api key below in order to properly connect to the Labelbox Client."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Add your api key\n",
        "API_KEY = \"\"\n",
        "client = lb.Client(api_key=API_KEY)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Create Ontology From Normalized Data\n",
        "* Users can create ontologies from a json definition of the ontology.\n",
        "* See below `OntologyBuilder` section for more details on constructing the normalized ontology.\n",
        "* Each tool type requires a specific value be passed:\n",
        "\n",
        "| Tool    | Value |\n",
        "| :----------- | :----------- |\n",
        "| Bounding box      | rectangle    |\n",
        "| Polygon           | polygon      |\n",
        "| Polyline          | line         |\n",
        "| Point             | point        |\n",
        "| Segmentation mask | raster-segmentation  |\n",
        "| Entity            | named-entity |\n",
        "| Relationship      | edge         |"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# This will automatically create new feature schema\n",
        "ontology_name = \"sdk-ontology\"\n",
        "feature_schema_cat_normalized = {\n",
        "    'tool': 'polygon',\n",
        "    'name': 'cat',\n",
        "    'color': 'black'\n",
        "}\n",
        "\n",
        "ontology_normalized_json = {\n",
        "    \"tools\": [feature_schema_cat_normalized],\n",
        "    \"classifications\": []\n",
        "}\n",
        "ontology = client.create_ontology(name=ontology_name,\n",
        "                                  normalized=ontology_normalized_json)\n",
        "print(ontology)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Create Ontology From Existing Feature Schemas\n",
        "* It is often useful to support the same features in multiple ontologies. \n",
        "* Labelbox supports this workflow by allowing users to create ontologies using existing feature schemas."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# First create the feature schema\n",
        "feature_schema_cat = client.create_feature_schema(feature_schema_cat_normalized)\n",
        "# When we create the ontology it will not re-create the feature schema\n",
        "print(feature_schema_cat.uid)\n",
        "ontology = client.create_ontology_from_feature_schemas(ontology_name,\n",
        "                                                       [feature_schema_cat.uid])"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Create Ontology From a Mix of New and Existing Feature Schemas\n",
        "* If we want to create a new ontology that expands upon a previous ontology it is helpful to be able to share a portion of the features.\n",
        "* To do this we will create the new schema ids that we want. Then we will create an ontology from the new list of ids.\n",
        "* Note that for additional customization you can also combine the normalized json and use the create_ontology() method (not covered here)."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Create new dog schema id\n",
        "feature_schema_dog_normalized = {\n",
        "    'tool': 'polygon',\n",
        "    'name': 'dog',\n",
        "    'color': 'black',\n",
        "    'classifications': [],\n",
        "}\n",
        "feature_schema_dog = client.create_feature_schema(feature_schema_dog_normalized)\n",
        "# The cat is shared between this new ontology and the one we created previously\n",
        "# (ie. the cat feature schema will not be re-created)\n",
        "ontology = client.create_ontology_from_feature_schemas(\n",
        "    ontology_name, [feature_schema_cat.uid, feature_schema_dog.uid])"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Read\n",
        "* We can directly query by id for ontologies and feature schemas\n",
        "* We also can search for both by name"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "#### Fetch by ID\n",
        "feature_schema = client.get_feature_schema(feature_schema_cat.uid)\n",
        "ontology = client.get_ontology(ontology.uid)\n",
        "print(feature_schema)\n",
        "print(ontology)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "#### Search by name\n",
        "feature_schema = next(client.get_feature_schemas(\"cat\"))\n",
        "ontology = next(client.get_ontologies(ontology_name))\n",
        "print(feature_schema)\n",
        "print(ontology)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Update and Delete"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Check if feature is archived\n",
        "feature_schema = next(client.get_feature_schemas(\"cat\"))\n",
        "client.is_feature_schema_archived(ontology_id=ontology.uid, feature_schema_id=feature_schema.uid)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Update a feature's title \n",
        "client.update_feature_schema_title(feature_schema_id=feature_schema.uid, title=\"cat-2\")\n",
        "feature = client.get_feature_schema(feature_schema_id=feature_schema.uid)\n",
        "print(\"Feature: \", feature)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Replace a feature \n",
        "tool = lb.Tool(feature_schema_id=feature_schema.uid, name=\"tool-cat-upserted\", tool=lb.Tool.Type.BBOX, color=\"#FF0000\")\n",
        "upserted_feature_schema_id = client.upsert_feature_schema(tool.asdict()).uid\n",
        "feature = client.get_feature_schema(feature_schema_id=upserted_feature_schema_id)\n",
        "print(\"Updated feature: \", feature)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Insert a new feature \n",
        "tool = lb.Tool(name=\"tool-cat-2\", tool=lb.Tool.Type.RASTER_SEGMENTATION)\n",
        "feature_schema_id_new = client.create_feature_schema(tool.asdict()).uid\n",
        "client.insert_feature_schema_into_ontology(feature_schema_id=feature_schema_id_new, ontology_id=ontology.uid , position=2)\n",
        "print(\"Updated ontology: \", client.get_ontology(ontology_id=ontology.uid))"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "Delete or Archived a feature:\n",
        "\n",
        "If the feature schema is a root level node with associated labels, it will be archived.\n",
        "If the feature schema is a nested node in the ontology and does not have associated labels, it will be deleted.\n",
        "If the feature schema is a nested node in the ontology and has associated labels, it will not be deleted."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "client.delete_feature_schema_from_ontology(ontology_id=ontology.uid, feature_schema_id=feature_schema_id_new)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Only features with annotations will be archived, features without annotations will be deleted. \n",
        "feature_schema_id_with_annotations = \"\" \n",
        "ontology_id = \"\"\n",
        "client.unarchive_feature_schema_node(ontology_id=ontology_id, root_feature_schema_id=feature_schema_id_with_annotations)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Ontology Builder\n",
        "* The ontology builder is a tool for creating and modifying normalized json"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Create normalized json with a bounding box and segmentation tool\n",
        "ontology_builder = lb.OntologyBuilder(tools=[\n",
        "    lb.Tool(tool=lb.Tool.Type.BBOX, name=\"dog\"),\n",
        "    lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name=\"cat\"),\n",
        "])\n",
        "# Creating an ontology from this is easy\n",
        "ontology = client.create_ontology(\"ontology-builder-ontology\",\n",
        "                                  ontology_builder.asdict())\n",
        "print(json.dumps(ontology.normalized, indent=2))"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "* Alternative syntax for defining the ontology via the OntologyBuilder"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Create\n",
        "ontology_builder = lb.OntologyBuilder()\n",
        "# Append tools\n",
        "tool_dog = lb.Tool(tool=lb.Tool.Type.BBOX, name=\"dog\")\n",
        "tool_cat = lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name=\"cat\")\n",
        "ontology_builder.add_tool(tool_dog)\n",
        "ontology_builder.add_tool(tool_cat)\n",
        "ontology = client.create_ontology(\"ontology-builder-ontology\",\n",
        "                                  ontology_builder.asdict())\n",
        "print(json.dumps(ontology.normalized, indent=2))"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "* Classifications are supported too (Both for top level and as subclassifications)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "ontology_builder = lb.OntologyBuilder(\n",
        "    tools=[\n",
        "        lb.Tool(tool=lb.Tool.Type.BBOX, name=\"dog\"),\n",
        "        lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION,\n",
        "             name=\"cat\",\n",
        "             classifications=[\n",
        "                 lb.Classification(class_type=lb.Classification.Type.TEXT,\n",
        "                                name=\"name\")\n",
        "             ])\n",
        "    ],\n",
        "    classifications=[\n",
        "        lb.Classification(class_type=lb.Classification.Type.RADIO,\n",
        "                       name=\"image_quality\",\n",
        "                       options=[lb.Option(value=\"clear\"),\n",
        "                                lb.Option(value=\"blurry\")])\n",
        "    ])\n",
        "print(json.dumps(ontology_builder.asdict(), indent=2))"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "Example of how to add sub-classfication within an option"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# We will use add_classification to add this classification to a previously built ontology_builder or you can create new ontology_builder = OntologyBuilder() \n",
        "radio_classification = lb.Classification(class_type=lb.Classification.Type.RADIO,\n",
        "                                      name=\"Global classification\",\n",
        "                                      options=[lb.Option(\"1st option\", options=[lb.Classification(class_type=lb.Classification.Type.CHECKLIST,\n",
        "                                                                                            name=\"Inside 1st option\",\n",
        "                                                                                            options=[lb.Option(\"Option A\"), lb.Option(\"Option B\")])]), lb.Option(\"2nd option\", options=[lb.Classification(class_type=lb.Classification.Type.CHECKLIST,\n",
        "                                                                                                                                                                                              name=\"Inside 2nd option\",\n",
        "                                                                                                                                                                                              options=[lb.Option(\"Option A\"), lb.Option(\"Option B\")])])])\n",
        "\n",
        "ontology_builder.add_classification(radio_classification)                                                                                                                                                                                         \n",
        "\n",
        "ontology = client.create_ontology(\"example of nested classification\",\n",
        "                                  ontology_builder.asdict())\n",
        "print(json.dumps(ontology.normalized, indent=2))"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "* All Tool objects are constructed the same way:"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "bbox_tool = lb.Tool(tool=lb.Tool.Type.BBOX, name=\"dog_box\")\n",
        "poly_tool = lb.Tool(tool=lb.Tool.Type.POLYGON, name=\"dog_poly\")\n",
        "seg_tool = lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name=\"dog_seg\")\n",
        "point_tool = lb.Tool(tool=lb.Tool.Type.POINT, name=\"dog_center\")\n",
        "line_tool = lb.Tool(tool=lb.Tool.Type.LINE, name=\"dog_orientation\")\n",
        "ner_tool = lb.Tool(tool=lb.Tool.Type.NER, name=\"dog_reference\")\n",
        "relationship_tool = lb.Tool(tool=lb.Tool.Type.RELATIONSHIP, name=\"relationship\")"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "* Classifications are all constructed the same way (except text which doesn't require options)\n",
        "* Classifications can be global or subclasses to a tool (ie dog bounding box, with a breed classification)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "text_classification = lb.Classification(class_type=lb.Classification.Type.TEXT,\n",
        "                                     name=\"dog_name\")\n",
        "radio_classification = lb.Classification(class_type=lb.Classification.Type.RADIO,\n",
        "                                      name=\"dog_breed\",\n",
        "                                      options=[lb.Option(\"poodle\")])\n",
        "checklist_classification = lb.Classification(\n",
        "    class_type=lb.Classification.Type.CHECKLIST,\n",
        "    name=\"background\",\n",
        "    options=[lb.Option(\"at_park\"), lb.Option(\"has_leash\")])"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    }
  ]
}